// standard
#include <vector>
#include <string>
#include <fstream>
#include <iostream>

// boost
#include <boost/regex.hpp>
#include <boost/foreach.hpp>
#include <boost/format.hpp>

// components
#include <index_string.h>
#include <ultoa.h>
#include <progress_indicator.h>
#include <print_error.h>
#include <matrix_io.h>
#include <matrix.h>
#define DEBUG_LEVEL 6
#include <debug_func.h>

#include "get_arg.h"

using boost::regex_search;
using std::vector;
using std::string;
using std::cout;
using std::cin;
using std::cerr;
using std::ifstream;
using std::ofstream;
using namespace bcpp_matrix;


struct t_iter_index
{
	unsigned index;
	vector<string>::const_iterator iter;
	t_iter_index(unsigned i, const vector<string>::const_iterator& it)
		:index(i), iter(it){}
	bool operator<( const t_iter_index& other)
	{
		return *iter < *(other.iter);
	}
	operator unsigned () const {return index;}

};

template <typename T>
void sort_indices_by_name(t_matrix_io_template<T>& matrix, vector<unsigned>& indices)
{
	vector<t_iter_index> named_indices;
	for (unsigned i = 0; i < indices.size(); ++i)
		named_indices.push_back(t_iter_index(indices[i], matrix.names.begin() + i));
	sort(named_indices.begin(),  named_indices.end());

	indices.assign(named_indices.begin(),  named_indices.end());
}

//________________________________________________________________________________________


//	filter_matrix_by_names_from_file

//			only select names matching those in file

//________________________________________________________________________________________
void index_unique_names(const vector<string>& names, t_index_string& indexed_names)
{
	// index current names
	for (unsigned i = 0; i < names.size(); ++i)
	{
		bool added;
		unsigned index = indexed_names.index(names[i], added);
		if (!added)
			throw std::runtime_error("The matrix contained separate entries (i.e rows " +
										ultoa(index + 1) + " and " + ultoa(i + 1) +
										") with identical names [" +
										names[i] + "]");
	}
}

template <typename T>
void filter_matrix_by_names_from_file(	const t_matrix_io_template<T>&	data,
										vector<unsigned>&				valid_indices,
										t_program_args&					args)
{
	if (args.names.length())
	{
		args.VERBOSE() << "\tMatching names in the supplied file...\n";
		ifstream istrm(args.names.c_str());
		if (!istrm)
			throw std::runtime_error("Could not open " + args.names);

		// index current names
		t_index_string curr_names;
		index_unique_names(data.names, curr_names);

		//
		//	go through valid names
		//        if valid names matches a current name, add the index of the current name
		//
		unsigned cnt_valid_names = 0;
		unsigned cnt_valid_names_duplicates = 0;
		t_index_string valid_names;
		cnt_valid_names = 0;
		string name;
		while (getline(istrm, name))
		{
			//
			//	make sure this valid_name has not been added before
			//
			bool added;
			valid_names.index(name, added);
			if (!added)
			{
				cnt_valid_names_duplicates++;
				continue;
			}

			unsigned index;
			if (curr_names.is_indexed(name, index))
				valid_indices.push_back(index);
			cnt_valid_names++;
		}

		if (cnt_valid_names == 0)
			args.VERBOSE() << "\nWARNING\n\t" << args.names << " is empty\n\n";
		else
		{
			args.VERBOSE() << "\t" << cnt_valid_names << " names entered.\n";
			args.VERBOSE() << "\t matching " << valid_indices.size() << " entries.\n";
			if (cnt_valid_names_duplicates)
				args.VERBOSE() << "\nWARNING\n\t" << cnt_valid_names_duplicates
								<< " of the supplied names were duplicates.\n\n";
		}
	}

	//
	// Unfiltered: add all entries
	//
	else
	{
		// index current names just to make sure no duplicates
		t_index_string curr_names;
		index_unique_names(data.names, curr_names);

		// add all names
		for (unsigned i = 0; i < data.names.size(); ++i)
			valid_indices.push_back(i);
		assert(valid_indices.size() == data.size());
	}
}
//8888888888888888888888888888888888888888888888888888888888888888888888888888888888888888

//	main

//8888888888888888888888888888888888888888888888888888888888888888888888888888888888888888
int main (int argc, char *argv[])
{
	debug_func_cerr(5);
	// we use cout to print progress so flush per call
	//std::ios::sync_with_stdio(false);

	t_program_args args;
	try
	{
	if (!process_command_line_options(argc, argv, args))
		return 1;





	//
	//	read data from input file
	//
	t_matrix_io_template<float> data;
	//ifstream ifs("human_chimp_exon1_matrix.txt");

	args.VERBOSE() << "\tReading matrix...\n";
	{
		t_progress_indicator dots(args.VERBOSE(), 1);
		dots.use_timer();
		unsigned cnt_duplicate_pairs = 0;

		//read_matrix(cin,  data,  args.input_format, cnt_duplicate_pairs, dots);
		read_matrix(cin,  data,  args.input_format, cnt_duplicate_pairs, dots);

		if (cnt_duplicate_pairs)
			args.VERBOSE() << "\t" << cnt_duplicate_pairs << " duplicate pairs found.\n";
	}


	//
	// only select names matching those in file
	//
	vector<unsigned> valid_indices;

	filter_matrix_by_names_from_file(data, valid_indices, args);



	//
	//	only select names matching regex
	//
	if (args.regex.length())
	{
		vector<unsigned> regex_valid_indices;
		args.VERBOSE() << "\tFiltering using the regular expression...\n";
		const boost::regex  regex_str(args.regex);
		for (unsigned i = 0; i < valid_indices.size(); ++i)
			if (regex_search(data.names[valid_indices[i]], regex_str, boost::match_default))
				regex_valid_indices.push_back(valid_indices[i]);
		regex_valid_indices.swap(valid_indices);
	}


	//
	//	some names alphabetically
	//
	if (args.alpha)
	{
		args.VERBOSE() << "\tSort matrix names by alphabetical order...\n";
		sort_indices_by_name(data, valid_indices);
	}


	data.reduce_to_subset(valid_indices);



	if (data.get_count_missing())
		args.VERBOSE() << "\t" << data.get_count_missing() << " missing values out of " <<
			data.get_count_pairs() << " in the matrix.\n";
	args.VERBOSE() << "\t" << data.size() << " rows/columns in the matrix.\n";

	if (args.short_names)
	{

		args.VERBOSE() << "\tConverting names to short forms\n";
		ofstream ofs(args.name_mapping_file.c_str());
		if (!ofs)
			throw std::runtime_error("Could not open " + args.name_mapping_file);
		args.VERBOSE() << "\tWriting map to " << args.name_mapping_file << "\n";


		unsigned index = 0;
		BOOST_FOREACH( string& long_name, data.names)
		{
			string short_name = (boost::format("XYZX%06d") % index++).str();
			ofs << short_name << "\t" << long_name << "\n";
			long_name = short_name;
		}
	}


	args.VERBOSE() << "\tWriting matrix...\n";
	if (!args.header)
		data.matrix_name = "";
	{
		t_progress_indicator dots(args.VERBOSE(), 10000);
		dots.use_timer();
		write_matrix(cout,  data,  args.output_format,  args.zero, dots);
	}
	cout.flush();



	args.VERBOSE() << "\tAll finished!!\n\n";
    }
    catch(std::exception& e)
    {
		std_print_error(args.CERR(),  e.what());
        return 1;
    }

	return 0;
}

